Skip to content

淺談 Entity Framework 的導覽屬性與外鍵的同步更新

TLDR

  • 導覽屬性(Navigation Property)同步的前提是相關的 Entity 必須處於追蹤(Tracked)狀態。
  • 任何觸發異動追蹤檢核的操作(如 AddEntrySaveChangesFind),都會自動同步更新導覽屬性與外鍵屬性。
  • 導覽屬性是否同步,不影響 SaveChanges 執行後的資料庫正確性,EF Core 會在儲存前自動進行檢核。
  • 使用 main.Subs.Remove(sub) 僅會解除關聯,若要刪除子表資料,必須使用 context.Subs.Remove(sub)
  • 若外鍵允許 null,解除關聯會將外鍵設為 null 而非刪除子表。

Entity 結構定義

本測試使用 Microsoft.EntityFrameworkCore 8。

csharp
public partial class Main {
    public long Id { get; set; }
    public virtual ICollection<Sub> Subs { get; set; } = new List<Sub>();
}

public partial class Sub {
    public long Id { get; set; }
    public long MainId { get; set; }
    public virtual Main Main { get; set; }
}

主表使用導覽屬性關聯子表

追蹤狀態對同步的影響

什麼情況下會遇到這個問題:在尚未呼叫 SaveChanges() 前,需要確認導覽屬性是否已自動關聯。

  • 未追蹤:若 mainsub 皆未加入追蹤,sub.Mainnull
  • 僅主表追蹤:當 main 加入追蹤後,會同步追蹤 subsub.Main 會自動更新。
  • 僅子表追蹤:若僅追蹤 sub 而不追蹤 mainsub.Main 不會同步更新。
  • 先追蹤後設定:若先追蹤 main 再執行 main.Subs.Add(sub)sub.MainSaveChanges() 前為 null,但執行後會自動同步。

子表使用導覽屬性關聯主表

什麼情況下會遇到這個問題:直接操作子表的導覽屬性來建立關聯時。

  • 未追蹤:直接設置 sub.Main = main,若兩者皆未追蹤,main.Subs 仍為空集合。
  • 僅主表追蹤:當 main 加入追蹤但 sub 未加入追蹤時,main.Subs 仍為空集合。
  • 僅子表追蹤:僅追蹤 sub 時,EF 會自動同步追蹤 main,此時 main.Subs 會包含 sub

使用外鍵屬性設定關聯

什麼情況下會遇到這個問題:透過直接修改 MainId 欄位來建立關聯時。

  • 僅追蹤子表:僅追蹤 sub 並設置 MainId,導覽屬性不會同步。
  • 雙方皆追蹤:在兩者皆被追蹤的情況下,導覽屬性會自動同步。
  • 追蹤後修改外鍵:若在加入追蹤後才設置 MainId,導覽屬性在 SaveChanges() 前不會同步,但執行後會更新。
  • Find() 取得資料:使用 Find() 取得已追蹤的 Main 資料時,導覽屬性會自動同步;若取得的是未追蹤的 Main,則不會同步。

其他操作與行為分析

SaveChanges 失敗與 Entry 呼叫

  • SaveChanges 失敗:即使 SaveChanges() 因資料庫限制(如主鍵重複)執行失敗,EF 內部的導覽屬性仍會完成同步。
  • Entry 觸發:執行 context.Entry(entity) 會強制觸發異動追蹤檢核,進而同步已追蹤 Entity 的導覽屬性。

刪除資料的注意事項

  • 刪除子表:必須使用 context.Subs.Remove(sub) 才能將資料從資料庫移除。
  • 解除關聯:使用 main.Subs.Remove(sub) 僅會解除關聯。
    • 若為多對多關聯,此操作會從聯結表中移除記錄。
    • 若外鍵允許 null,此操作會將 sub.MainId 設為 null,而不會刪除子表資料。

異動歷程

    • 初版文件建立。
    • 補上對應 GitHub 範例專案連結。